package io.gitee.geminidev.bot.websocket;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import cn.hutool.log.StaticLog;
import io.gitee.geminidev.bot.Bot;
import io.gitee.geminidev.bot.constant.OPCodeConsts;
import io.gitee.geminidev.bot.domain.BaseMessage;
import io.gitee.geminidev.bot.domain.HeadMessage;
import io.gitee.geminidev.bot.type.Code;
import io.gitee.geminidev.bot.type.EventType;
import io.gitee.geminidev.bot.event.Callback;
import org.java_websocket.exceptions.WebsocketNotConnectedException;
import org.java_websocket.handshake.ServerHandshake;

import java.net.URI;
import java.util.*;

/**
 * @Author gemini
 * @Date 2023/4/2 23:17
 * @description:
 */
public class BotWebSocketClient extends AbstractBotWebSocket implements BotWebSocket {

    /**
     * 构造方法
     *
     * @param serverURI
     */
    public BotWebSocketClient(URI serverURI, String token) {
        super(serverURI);
        this.token = token;
    }

    /**
     * 打开连接是方法
     *
     * @param serverHandshake
     */
    @Override
    public void onOpen(ServerHandshake serverHandshake) {
        if(hasReconnect.get()){
            // 重连
            resume();
        }
        StaticLog.info("BotWebSocketClient onOpen");
    }



    /**
     * 客户端恢复
     */
    private void resume(){
        JSONObject json = new JSONObject();
        JSONObject d = new JSONObject();

        d.set("token", token);
        d.set("session_id", sessionId);
        d.set("seq", serialNumber);

        json.put("op", OPCodeConsts.RESUME);
        json.put("d", d);

        String message = JSONUtil.toJsonStr(json);
        send(message);
    }

    /**
     * 收到消息时
     *
     * @param s
     */
    @Override
    public void onMessage(String s) {
        final HeadMessage head;
        hasMessage.set(true);
        StaticLog.info("onMessage:" + s);

        if(!StrUtil.isEmpty(s) && JSONUtil.isTypeJSON(s)){
            JSONObject json = JSONUtil.parseObj(s);
            head = json.toBean(HeadMessage.class);

            switch (head.getOp()){
                case OPCodeConsts.HELLO -> {
                    // Hello
                    if(hasReconnect.get()){
                        return;
                    }
                    int heartbeatInterval = json.getJSONObject("d").getInt("heartbeat_interval");

                    // 设置心跳
                    Timer timer = new Timer();
                    timer.schedule(new HeartbeatTimer(), heartbeatInterval, heartbeatInterval);

                    // 鉴权
                    JSONObject identify = new JSONObject();
                    identify.set("token", token);
                    identify.set("intents", this.intents);
                    identify.set("shard", Arrays.asList(new Integer[]{0, 1}));
                    identify.set("properties", new JSONObject());
                    json.put("d", JSONUtil.parse(identify));
                    json.put("op", OPCodeConsts.IDENTIFY);
                    String message = JSONUtil.toJsonStr(json);

                    send(message);
                }
                case OPCodeConsts.DISPATCH -> {
                    // 触发事件
                    EventType eventType = Enum.valueOf(EventType.class, json.getStr("t"));
                    if(EventType.READY == eventType){
                        sessionId = json.getJSONObject("d").getStr("session_id");
                    }

                    LinkedHashSet<Callback> group = callbacks.getGroup(eventType);
                    if(CollectionUtil.isEmpty(group)){
                        return;
                    }
                    for (Callback callback : group) {
                        BaseMessage d = json.getJSONObject("d").toBean(eventType.clazz());
                        d.setHead(head);
                        if(callback == null){
                            continue;
                        }
                        Code code = callback.event(d);
                        if(Code.EXIT == code){
                            break;
                        }
                    }

                    String messageId = json.getJSONObject("d").getStr("id");
                    if(!StrUtil.isEmpty(messageId)){
                        boolean delete = FileUtil.del(FileUtil.file(Bot.getApplicationiTempDirectory(), messageId));
                        if(!delete){
                            StaticLog.error("临时目录删除失败: {}", FileUtil.file(Bot.getApplicationiTempDirectory(), messageId).getPath());
                        }
                    }
                }
            }
        }
    }

    /**
     * 当连接关闭时
     *
     * @param i
     * @param s
     * @param b
     */
    @Override
    public void onClose(int i, String s, boolean b) {
        this.hasConnection.set(false);
        this.hasMessage.set(false);
        StaticLog.warn("onClose:" + s);
    }

    /**
     * 发生error时
     *
     * @param e
     */
    @Override
    public void onError(Exception e) {
        StaticLog.error("onError:" + e.getMessage(), e);
    }

    @Override
    public void connect() {
        if(!this.hasConnection.get()){
            super.connect();
            hasConnection.set(true);
            hasReconnect.set(false);
        }
    }

    @Override
    public void reconnect(){
        super.reconnect();
        hasReconnect.set(true);
    }

    @Override
    public void start() {
        this.connect();
    }

    @Override
    public void setIntents(int intents) {
        this.intents = intents;
    }

    @Override
    public void on(EventType eventType, Callback callback) {
        if(callback == null){
            return;
        }
        callbacks.put(eventType, callback);
    }

    @Override
    public void send(String text){
        try {
            super.send(text);
            StaticLog.info("send message:{}", text);
        } catch (WebsocketNotConnectedException websocketNotConnectedException) {
            // 断开重连
            reconnect();
        }
    }

    class HeartbeatTimer extends TimerTask {

        @Override
        public void run() {
            JSONObject json = new JSONObject();
            json.set("op", OPCodeConsts.HEART_BEAT);
            json.set("d", serialNumber.get());
            // 发送心跳
            send(JSONUtil.toJsonStr(json));
        }
    }
}
